使用 NSProxy 代理解决 NSTimer 内存泄漏
NSProxy 是什么?
NSProxy
是一个抽象根类。换句话说,
- 它没有父类;
- 它是一个抽象类,没有具体实现。
因此它常常作为其他对象或尚不存在对象的代理,定义对象的 API。
关于 NSProxy 的简单介绍
通常,当 proxy
收到消息时,它会将消息转发给实际的处理对象,或加载/转换为实际的消息处理对象进行处理。NSProxy
的子类可以用于实现透明的消息转发(例如 NSDistantObject
),或者用于延迟资源消耗较大对象的创建时机。
NSProxy
实现了作为根类需要实现的基本方法(包括 NSObject 协议方法),同时作为抽象类,proxy
不能提供初始化方法,当收到未实现的消息时抛出异常。proxy
正确实现的子类,必须提供一个初始化或创建方法,并且重写 forwardInvocation:
和 methodSignatureForSelector:
方法来处理它本身未实现的消息。
forwardInvocation:
将给定的调用传递给 proxy 实际代理的对象
methodSignatureForSelector:
默认抛出NSInvalidArgumentException
,重写这个方法可以为proxy
代理对象返回一个合适的NSMethodSignature
对象,
NSProxy 为什么可以解决 NSTimer 循环引用?
循环引用情况分析:VC -> timer -> VC(target), runloop -> timer
基于 NSProxy 消息转发的特性,让 NSProxy 子类弱持有 target,将收到的消息转发给实际的消息处理类,即 VC。
此时的引用情况: VC —> timer —> Proxy --> VC(target), runloop —> timer
循环引用此时便被打破了,这样做的 好处 是易拓展,低耦和,符合设计模式中的开闭原则和迪米特法则。因此 在一些开源框架中常常使用 proxy
持有一个弱引用的 target
, 来打破 NSTimer
、CADisplayLink
与 target
之间的循环引用。
解决的其它正确姿势:
iOS 10 之后 NSTimer 提供了 block 支持的API,而 iOS 10 之前循环引用的常见解决方式:
VC
弱引用 timer,在VC
的viewWillDisappear:
中调用invalidate
废弃timer
引用情况为VC --> timer —> VC(target), runloop —> timer
添加
NSTimer
分类 结合Block
调用定时方法,VC
的dealloc
方法中调用invalidate
VC —> timer —> NSTimer(target 类方法执行 block), runloop —> timer
使用中间类进行解耦,中间类弱引用 target并重写消息转发方法
forwardInvocation
让VC
处理消息VC —> timer —> MiddleClass(消息转发给 target 处理) --> VC(target), runloop —> timer
最后一点本质上和使用 NSProxy 相同,而使用 Proxy 代理的优点是:可以直接进行消息转发,和第三点相比跳过了消息派发和动态方法解析阶段,因此效率较高一些。
错误的解决方式:
|
|
即使 weakSelf
并将 target
指向 weakSelf
, timer
还是会强引用 self
。因为所有权修饰符无论是 __weak
还是 __strong
,在 NSTimer
中都会生成新的强引用指针重新指向,导致循环引用的。
apple 文档:The timer maintains a strong reference to target until it (the timer) is invalidated.
Proxy 怎么用?
YYImage 内 Proxy 比较规范的实现:
|
|